package org.metasketch.javafx.graphics;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;

import org.metasketch.diagram.javafx.graphics.BaseGraphic;
import org.metasketch.diagram.javafx.graphics.BaseLink;
import org.metasketch.editor.toolbox.IInputHandler;
import org.metasketch.graphics.Diagram;
import org.metasketch.graphics.GraphicContainer;
import org.metasketch.graphics.Point;
import org.metasketch.model.BaseModel;
import org.metasketch.model.CommonIdentified;
import org.metasketch.model.IdDatabase;

import com.thoughtworks.xstream.annotations.XStreamOmitField;

public class JFXDiagram extends ScrollPane implements EventHandler<MouseEvent> {

	Diagram diagram;

	BaseModel model;
	// String name;

	Pane root = new Pane();

	// @XStreamOmitField
	IdDatabase index = new IdDatabase();
	Map<String, IJFXGraphicNode> graphicsPerId = new HashMap<String, IJFXGraphicNode>();

	Group nodes = new Group();
	Group links = new Group();

	private IInputHandler inputHandler;

	public JFXDiagram() {

		// this.set
		// this.setNode(root);
		this.setContent(root);
		root.getChildren().addAll(nodes, links);

		// root.setStyle("-fx-background-color: #fffff0");

		// root.setStyle("-fx-background-color: red");

		this.setHbarPolicy(ScrollBarPolicy.AS_NEEDED);
		this.setVbarPolicy(ScrollBarPolicy.AS_NEEDED);

		// root.set

		// setScene(scene);
		setOnMouseClicked((EventHandler<? super MouseEvent>) this);
		setOnMouseDragged((EventHandler<? super MouseEvent>) this);
		setOnMouseEntered((EventHandler<? super MouseEvent>) this);
		setOnMouseExited((EventHandler<? super MouseEvent>) this);
		setOnMouseMoved((EventHandler<? super MouseEvent>) this);
		setOnMousePressed((EventHandler<? super MouseEvent>) this);
		setOnMouseReleased((EventHandler<? super MouseEvent>) this);

	}

	public List<BaseGraphic> getGraphicNodeSemanticClass(Class type) {
		List<BaseGraphic> result = new ArrayList<BaseGraphic>();
		ObservableList<Node> children = nodes.getChildren();
		for (Node node : children) {
			if (node instanceof BaseGraphic)
				if (type.isAssignableFrom(((BaseGraphic) node).getSemantic()
						.getClass()))
					result.add((BaseGraphic) node);
		}

		return result;
	}

	public BaseGraphic getClosestNode(Point point, CommonIdentified target) {
		ObservableList<Node> children = nodes.getChildren();

		SearchBean sbean = new SearchBean();

		recursiveGetNode(point, target, children, sbean);

		return (BaseGraphic) sbean.getTargetNode();

	}

	public BaseLink getClosestLink(Point point, CommonIdentified target) {
		ObservableList<Node> children = links.getChildren();

		SearchBean sbean = new SearchBean();

		recursiveGetNode(point, target, children, sbean);

		return (BaseLink) sbean.getTargetNode();

	}

	private void recursiveGetNode(Point point, CommonIdentified target,
			ObservableList<Node> children, SearchBean searchBean) {

		for (Node node : children) {

			if (node instanceof BaseGraphic) {
				BaseGraphic bgraphic = (BaseGraphic) node;
				if (bgraphic.getSemantic() == target) {
					double distance = point.getDistance(bgraphic
							.getCompleteCoordinatesThroughParent());

					if (searchBean.getTargetDistance() == null
							|| distance < searchBean.getTargetDistance()) {
						searchBean.setTargetDistance(distance);
						searchBean.setTargetNode(bgraphic);
					}
				}

				if (bgraphic.getGraphicNode() instanceof GraphicContainer) {
					ObservableList<Node> grandChildren = bgraphic.getChildren();
					recursiveGetNode(point, target, grandChildren, searchBean);
				}
			}

			if (node instanceof BaseLink) {
				BaseLink blink = (BaseLink) node;
				if (blink.getSemantic() == target) {

					double sourceDistance = point.getDistance(blink.getSource()
							.getCompleteCoordinatesThroughParent());

					double targetDistance = point.getDistance(blink.getTarget()
							.getCompleteCoordinatesThroughParent());

					double distance = sourceDistance + targetDistance;

					if (searchBean.getTargetDistance() == null
							|| distance < searchBean.getTargetDistance()) {
						searchBean.setTargetDistance(distance);
						searchBean.setTargetNode(blink);
					}
				}

				// if (bgraphic.getGraphicNode() instanceof GraphicContainer) {
				// ObservableList<Node> grandChildren = bgraphic.getChildren();
				// recursiveGetNode(point, target, grandChildren, searchBean);
				// }
			}

		}
		// return targetDistance;
	}

	public List<BaseGraphic> getNodesAt(double x, double y) {

		List<BaseGraphic> selectedGraphics = new ArrayList<BaseGraphic>();

		ObservableList<Node> insideNodes = nodes.getChildren();

		// List<BaseGraphic> graphics = diagram.getGraphics();
		for (Node node : insideNodes) {
			if (node instanceof BaseGraphic
					&& ((BaseGraphic) node).contains(x, y))
				selectedGraphics.add((BaseGraphic) node);
		}

		return selectedGraphics;
	}

	public List<BaseGraphic> getNodesAt(double x, double y, double width,
			double height) {

		List<BaseGraphic> selectedGraphics = new ArrayList<BaseGraphic>();

		ObservableList<Node> insideNodes = nodes.getChildren();

		// List<BaseGraphic> graphics = diagram.getGraphics();
		for (Node node : insideNodes) {
			if (node instanceof BaseGraphic
					&& ((BaseGraphic) node).inside(x, y, width, height))
				selectedGraphics.add((BaseGraphic) node);

			// if(node instanceof GraphicContainer)
			// {
			// container
			// }

		}

		return selectedGraphics;
	}

	// public Group getRoot() {
	// return root;
	// }

	// make sure that whatever graphic nodes are added to the diagram have their
	// mouse events pass through here
	public void addNode(Node node) {
		addBlindNode(node);

		addNodeListeners(node);

	}

	public void addNodeListeners(Node node) {
		node.setOnMouseClicked((EventHandler<? super MouseEvent>) this);
		node.setOnMouseDragged((EventHandler<? super MouseEvent>) this);
		node.setOnMouseEntered((EventHandler<? super MouseEvent>) this);
		node.setOnMouseExited((EventHandler<? super MouseEvent>) this);
		node.setOnMouseMoved((EventHandler<? super MouseEvent>) this);
		node.setOnMousePressed((EventHandler<? super MouseEvent>) this);
		node.setOnMouseReleased((EventHandler<? super MouseEvent>) this);

		if (node instanceof IJFXGraphicNode) {
			IJFXGraphicNode gnode = (IJFXGraphicNode) node;
			model.getModelChangeManager().registerListener(gnode.getSemantic(),
					gnode);
			gnode.setModelChangeManager(model.getModelChangeManager());

			if (gnode instanceof BaseLink) {
				BaseLink link = (BaseLink) gnode;
				if (link.getSource() != null)
					model.getModelChangeManager().registerListener(
							link.getSource().getSemantic(), gnode);

				if (link.getTarget() != null)
					model.getModelChangeManager().registerListener(
							link.getTarget().getSemantic(), gnode);
			}

		}
	}

	public void removeNode(Node node) {
		nodes.getChildren().remove(node);

		if (node instanceof BaseGraphic) {
			BaseGraphic baseGraphic = (BaseGraphic) node;
			index.removeNode(baseGraphic.getGraphicNode());
			graphicsPerId.remove(baseGraphic.getGraphicNode().getId());
		}

		if (node instanceof IJFXGraphicNode) {
			IJFXGraphicNode gnode = (IJFXGraphicNode) node;
			model.getModelChangeManager().unregisterListener(gnode);
			gnode.setModelChangeManager(null);
		}

		// node.reOnMouseClicked((EventHandler<? super MouseEvent>) this);
		// node.setOnMouseDragged((EventHandler<? super MouseEvent>) this);
		// node.setOnMouseEntered((EventHandler<? super MouseEvent>) this);
		// node.setOnMouseExited((EventHandler<? super MouseEvent>) this);
		// node.setOnMouseMoved((EventHandler<? super MouseEvent>) this);
		// node.setOnMousePressed((EventHandler<? super MouseEvent>) this);
		// node.setOnMouseReleased((EventHandler<? super MouseEvent>) this);
	}

	public void addNodes(List nodes) {
		for (Object node : nodes) {
			addNode((Node) node);
		}
	}

	public void addBlindNode(Node node) {

		if (node instanceof BaseGraphic) {
			BaseGraphic baseGraphic = (BaseGraphic) node;
			index.storeNode(baseGraphic.getGraphicNode());
			graphicsPerId
					.put(baseGraphic.getGraphicNode().getId(), baseGraphic);
			nodes.getChildren().add(node);

			// don't forget to add the children to the index as well !!!
			addChildrenToIndex(baseGraphic);

		} else if (node instanceof BaseLink) {
			BaseLink baseGraphic = (BaseLink) node;
			index.storeNode(baseGraphic.getGraphicNode());
			graphicsPerId
					.put(baseGraphic.getGraphicNode().getId(), baseGraphic);
			links.getChildren().add(node);
		} else
			nodes.getChildren().add(node);

	}

	private void addChildrenToIndex(BaseGraphic baseGraphic) {
		if (baseGraphic.getGraphicNode() instanceof GraphicContainer) {
			ObservableList<Node> children = baseGraphic.getChildren();
			for (Node child : children) {
				if (child instanceof BaseGraphic) {

					BaseGraphic childGraphic = (BaseGraphic) child;

					index.storeNode(childGraphic.getGraphicNode());
					graphicsPerId.put(childGraphic.getGraphicNode().getId(),
							childGraphic);

					addChildrenToIndex(childGraphic);
				}
			}
		}
	}

	public IJFXGraphicNode getBaseGraphicPerId(String id) {
		return graphicsPerId.get(id);
	}

	public Diagram getDiagram() {
		return diagram;
	}

	public void setDiagram(Diagram diagram) {
		this.diagram = diagram;
	}

	public BaseModel getModel() {
		return model;
	}

	public void setModel(BaseModel model) {
		this.model = model;
	}

	public void setInputHandler(IInputHandler inputHandler) {
		this.inputHandler = inputHandler;

	}

	@Override
	public void handle(MouseEvent event) {
		// System.out.println("Diagram Event: " + event);
		// System.out.println(arg0.getSceneX() - get + ", " + arg0.getSceneY());
		inputHandler.handle(event, this, model);
	}

	// public Scene getScene() {
	// return scene;
	// }

	public Group getNodes() {
		return nodes;
	}

	public Group getLinks() {
		return links;
	}

}
